/*
 * Main.java
 *
 * Created on March 26, 2007, 2:47 PM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package org.atomojo.app;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.text.DateFormat;
import java.util.Date;
import java.util.Map;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import org.atomojo.app.db.ConfigurationException;
import org.atomojo.app.db.DB;
import org.infoset.xml.XMLException;
import org.infoset.xml.util.WriterItemDestination;


/**
 *
 * @author alex
 */
public class Main implements Runnable {
   
static String fineLog =
"handlers= java.util.logging.ConsoleHandler\n"+
".level= FINE\n"+
"java.util.logging.ConsoleHandler.level = FINE\n"+
"java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter\n";
;
static String finerLog =
"handlers= java.util.logging.ConsoleHandler\n"+
".level= FINER\n"+
"java.util.logging.ConsoleHandler.level = FINER\n"+
"java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter\n";
;
static String finestLog =
"handlers= java.util.logging.ConsoleHandler\n"+
".level= FINEST\n"+
"java.util.logging.ConsoleHandler.level = FINEST\n"+
"java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter\n";
;
   static String defaultStorageClass = "org.atomojo.app.storage.file.FileStorageFactory";
   static {
      String value = System.getProperty("org.atomojo.app.storage.class");
      if (value!=null) {
         defaultStorageClass = value;
      }
   }
   boolean create;
   File dir;
   String [] args;
   Map<String,DB> dbList;
   WebComponent web;
   ServerConfiguration conf;
   Level logLevel;
   Logger log;
   int argIndex;
   String storageClassName = null;
   
   class DebugHandler extends Handler {
      public void publish(LogRecord record) {
         if (record.getLevel().intValue()<=Level.FINE.intValue()) {
            DateFormat format = DateFormat.getDateTimeInstance();
            String dateTime = format.format(new Date(record.getMillis()));
            System.err.println(dateTime+" "+record.getSourceClassName()+" "+record.getSourceMethodName());
            System.err.println(record.getLevel()+": "+record.getMessage());
         }
      }
      
      public void flush() {
         
      }
      
      public void close() {
         
      }
   }
   
   /** Creates a new instance of Main */
   public Main(String [] args) {
      this.argIndex = 0;
      this.logLevel = Level.INFO;
      while (argIndex<args.length && args[argIndex].charAt(0)=='-') {
         String name = args[argIndex].substring(1);
         argIndex++;
         if (argIndex==args.length) {
            throw new RuntimeException("The argument "+args[argIndex]+" requires an argument.");
         }
         if (name.equals("l") || name.equals("-level")) {
            if (args[argIndex].equals("info")) {
               logLevel = Level.INFO;
            } else if (args[argIndex].equals("fine")) {
               logLevel = Level.FINE;
            } else if (args[argIndex].equals("finer")) {
               logLevel = Level.FINER;
            } else if (args[argIndex].equals("finest")) {
               logLevel = Level.FINEST;
            } else if (args[argIndex].equals("config")) {
               logLevel = Level.CONFIG;
            }
         } else if (name.equals("c") || name.equals("-class")) {
            storageClassName = args[argIndex];
         }
         argIndex++;
      }
      int argCount = args.length-argIndex;
      
      if (argCount!=1 && argCount!=4) {
         throw new RuntimeException("The number of arguments is wrong.");
      }
      

      this.dir = new File(args[argIndex]);
      if (!dir.exists()) {
         throw new RuntimeException("The directory "+dir.getAbsolutePath()+" does not exist.");
      }
      if (!dir.canWrite()) {
         throw new RuntimeException("Cannot write to the directory "+dir.getAbsolutePath());
      }
      
      this.args = args;
      this.dbList = null;
      this.conf = null;
      this.web = null;
   }

   /**
    * @param args the command line arguments
    */
   public static void main(String[] args) {
      if (args.length<1) {
         System.err.println("atomapp {options} dir { hostname ipaddress port }");
         System.exit(1);
      }
      
      try {
         Main main = new Main(args);
         try {
            main.init();
            main.run();
         } catch (Exception ex) {
            ex.printStackTrace();
         }
      } catch (RuntimeException ex) {
         System.err.println(ex.getMessage());
      }
   }
   
   StorageFactory getStorageFactory() 
      throws Exception
   {
      final Class<StorageFactory> storageClass = (Class<StorageFactory>)this.getClass().forName(storageClassName);
      return storageClass.newInstance();
   }
   
   public void init() 
      throws ConfigurationException,IOException,XMLException
   {
      
      System.out.println("Configuring...");
      
      System.out.println("Setting log level to "+logLevel);
      log = Logger.getLogger(WebComponent.LOG_NAME);
      log.setLevel(logLevel);
      log = Logger.getLogger("org.atomojo");
      log.setLevel(logLevel);
      log.addHandler(new DebugHandler());

      if (logLevel==Level.FINE) {
         try {
            LogManager.getLogManager().readConfiguration(new ByteArrayInputStream(fineLog.getBytes()));
         } catch (java.io.IOException ex) {
            ex.printStackTrace();
         }
      } else if (logLevel==Level.FINER) {
         try {
            LogManager.getLogManager().readConfiguration(new ByteArrayInputStream(finerLog.getBytes()));
         } catch (java.io.IOException ex) {
            ex.printStackTrace();
         }
      } else if (logLevel==Level.FINEST) {
         try {
            LogManager.getLogManager().readConfiguration(new ByteArrayInputStream(finestLog.getBytes()));
         } catch (java.io.IOException ex) {
            ex.printStackTrace();
         }

      }

      dbList = DB.getDatabases(log,dir);
      
      conf = new ServerConfiguration();
      File serverConfFile = new File(dir,"server.conf");
      if (serverConfFile.exists()) {
         create = false;
         conf.setLogDirectory(new File(dir,"logs"));
         conf.load(serverConfFile.toURI());
         initStorageClass();
         try {
            web = new WebComponent(dir,dbList,getStorageFactory(),conf);
         } catch (Exception ex) {
            throw new ConfigurationException("Cannot load storage "+storageClassName,ex);
         }
      } else {
         create = true;
      }
      
         
   }
   
   protected void initStorageClass() {
      if (storageClassName==null) {
         storageClassName = conf.getStorageClassName();
      }
      if (storageClassName==null) {
         storageClassName = defaultStorageClass;
      }
      
      log.info("Using storage class "+storageClassName);
   }
   
   public void run() {
      
      try {
         
         if (create) {
            if (dbList.isEmpty()) {
               Logger log = Logger.getLogger(WebComponent.LOG_NAME);
               DB db = DB.createDB(log,dir,"data");
               dbList.put(db.getName(),db);            
               try {
                  DB.writeList(dir,dbList);
               } catch (Exception ex) {
                  ex.printStackTrace();
                  return;
               }
            }
            int argCount = args.length-argIndex;
            String hostname = argCount>1 ? args[argIndex+1] : "*";
            String ipAddress = argCount>1 ? args[argIndex+2] : "*";
            int port = argCount>1 ? Integer.parseInt(args[argIndex+3]) : 8080;

            File logDir = new File(dir,"logs");
            ServerConfiguration.Host host = new ServerConfiguration.Host(conf,"data",hostname,null,port,false,false,new File(logDir,hostname.equals("*") ? "any.log" : hostname+".log"));
            conf.getInterfaces().add(new ServerConfiguration.Interface(ipAddress,port,true));
            conf.getHosts().put(host.getName(),host);
            File keystoreFile = new File(dir,"keystore");
            conf.setKeystoreFile(keystoreFile);
            conf.setKeystorePassword("atomojo");
            conf.setKeyPassword("atomojo");
            conf.setLogDirectory(logDir);
            conf.setTempDirectory(dir);
            if (storageClassName!=null) {
               conf.setStorageClassName(storageClassName);
            }
            File serverConfFile = new File(dir,"server.conf");
            Writer out = new OutputStreamWriter(new FileOutputStream(serverConfFile),"UTF-8");
            WriterItemDestination dest = new WriterItemDestination(out,"UTF-8");
            conf.store(serverConfFile.toURI(),dest);
            out.flush();
            out.close();
            if (!keystoreFile.exists()) {
               copyResource("/org/atomojo/app/db/conf/keystore",keystoreFile);
            }
            initStorageClass();
            web = new WebComponent(dir,dbList,getStorageFactory(),conf);
         }
         System.out.println("Connecting to databases...");
         // Connect to all databases 
         for (DB db : dbList.values()) {
            System.out.println("Connecting to DB "+db.getName());
            db.connect();
         }
         
         
         System.out.println("Starting server...");

         
         web.start();
         
         System.out.println("Started.");
         
      } catch (Exception ex) {
         ex.printStackTrace();
      }
   }
   
   public void stop() 
      throws Exception
   {
      web.stop();
   }
   
   public static void copyResource(String resourcePath,File outFile)
      throws IOException
   {
      InputStream in = Main.class.getResourceAsStream(resourcePath);
      if (in==null) {
         throw new IOException("Cannot open resource "+resourcePath);
      }
      FileOutputStream out = new FileOutputStream(outFile);
      byte [] buffer = new byte[8192];
      int len;
      while ((len=in.read(buffer))>0) {
         out.write(buffer,0,len);
      }
      out.close();
      in.close();
   }
   
}
